package dk.zpon.andnoise;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.util.Log;

class Connection {

	// Singleton
	private static final Connection INSTANCE = new Connection();

	// Private constructor prevents instantiation from other classes
	private Connection() {}

	public static Connection getInstance() {
		return INSTANCE;
	}


	private String mHost;
	private int mPort; 
	private Usertype mUsertype;
	private String mPassword;
	private Socket mSocket;
	private PrintWriter mOut;
	private BufferedReader mIn;
	private String mCurrentPath = "/";

	public String getHost() {
		return mHost;
	}

	public void setHost(String nhost) {
		mHost = nhost;
	}

	public int getPort() {
		return mPort;
	}

	public void setPort(int nport) {
		mPort = nport;
	}

	public Usertype getUsertype() {
		return mUsertype;
	}

	public void setUsertype(Usertype nusertype) {
		mUsertype = nusertype;
	}

	public String getPassword() {
		return generaPassword(mPassword);
	}

	public void setPassword(String npassword) {
		mPassword = npassword;
	}

	public String generaPassword(String string)
	{
		String hashtext = "";
		try{
			MessageDigest m = MessageDigest.getInstance("MD5");
			m.reset();
			m.update(string.getBytes());
			byte[] digest = m.digest();
			BigInteger bigInt = new BigInteger(1,digest);
			hashtext = bigInt.toString(16);
			// Now we need to zero pad it if you actually want the full 32 chars.
			while(hashtext.length() < 32 ){
				hashtext = "0"+hashtext;
			}
		} catch (NoSuchAlgorithmException e) {

		}

		return hashtext;
	}

	public Socket getSocket() {
		return mSocket;
	}

	public void setSocket(Socket socket) {
		this.mSocket = socket;
	}

	public PrintWriter getOut() {
		return mOut;
	}

	public void setOut(PrintWriter out) {
		this.mOut = out;
	}

	public BufferedReader getIn() {
		return mIn;
	}

	public void setIn(BufferedReader in) {
		this.mIn = in;
	}

	/**
	 * Connects to the server
	 * @return an integer, 1 if connected, 0 if no reply from server and -1 if wrong connection information
	 */
	public int connect()
	{
		if(this.mHost.length() != 0 && this.mUsertype != null)
		{
			try
			{
				mSocket = new Socket(this.mHost, this.mPort);

				mOut = new PrintWriter( new BufferedWriter( new OutputStreamWriter(mSocket.getOutputStream())),true);
				mIn = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
				
				mOut.println("{\"type\" : \"request\", \"command\" : \"LOGI\", \"data\" : {\"TYPE\" : \""+getUsertype()+"\", \"PASS\" : \""+getPassword()+"\"}}");

				while(!mIn.ready()){}

				JSONObject json = new JSONObject(mIn.readLine());

				if(json.getJSONObject("data").get("LOGIN").toString() == "true")
				{
					return 1;
				}
				else
					return -1;
			}
			catch(Exception e)
			{
				e.printStackTrace();
				return 0;
			}
		}
		else
			return -1;
	}

	/*
	 * GET INFORMATION
	 */
	public String getTitle() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GTIT\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getJSONObject("data").getString("TITLE").toString();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public String getArtist() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GART\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getJSONObject("data").getString("ARTIST").toString();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public String getAlbum() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GALB\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getJSONObject("data").getString("ALBUM").toString();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public int getLength() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GLEN\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getJSONObject("data").getInt("LENGTH");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public int getPosition() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GPOS\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getJSONObject("data").getInt("POSITION");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public JSONObject getInfo() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"GINF\"}");
		JSONObject returnobj = null;
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());

			if(json.getString("type").equals("response"))
			{
				returnobj = json.getJSONObject("data");
			}
			else if(json.getString("type").equals("error"))
			{
				throw new NotPlayingException();
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
		return returnobj;
	}

	/*
	 * SET INFORMATION
	 */
	public boolean setVolume(int volume) throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"SVOL\", \"data\" : {\"VOLUME\" : " + new Integer(volume).toString() +"}}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getString("type").equals("response") && json.getString("command").equals("SVOL");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public boolean setPosition(int position) throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"SPOS\", \"data\" : {\"POSITION\" : " + new Integer(position).toString() +"}}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			
			return json.getString("type").equals("response") && json.getString("command").equals("SPOS");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	/*
	 * PLAYBACK
	 */
	public boolean playbackPlay() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"PPLA\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getString("type").equals("response") && json.getString("command").equals("PPLA");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}

	public boolean playbackPause() throws Exception
	{
		mOut.println("{\"type\" : \"request\", \"command\" : \"PPAU\"}");
		try {
			while(!mIn.ready()){}

			JSONObject json = new JSONObject(mIn.readLine());
			return json.getString("type").equals("response") && json.getString("command").equals("PPAU");

		} catch (Exception e) {
			// TODO Auto-generated catch block
			throw e;
		}
	}
	
	public boolean playbackOpen(String s)
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"POPE\", \"data\" : {\"FILE\" : \""+s+"\"}}")) != null)
		{
			try {
				return json.getString("type").equals("response") && json.getString("command").equals("POPE");
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	}

	public boolean playbackNext()
	{
		JSONObject json;
		if((json = this.makeRequest("{\"type\" : \"request\", \"command\" : \"PNEX\"}")) != null)
		{
			try {
				if(json.getString("type") == "response" && json.getString("command") == "PNEX")
				{
					return true;
				}
				else
					return false;
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		return false;
	}

	public boolean playbackPrevious()
	{
		JSONObject json;
		if((json = this.makeRequest("{\"type\" : \"request\", \"command\" : \"PPRE\"}")) != null)
		{
			try {
				if(json.getString("type") == "response" && json.getString("command") == "PPRE")
				{
					return true;
				}
				else
					return false;
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		return false;
	}
	
	/*
	 * PLAYLIST
	 */

	public int playlistGetLength()
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"LLEN\"}")) != null)
		{
			try {
				return json.getJSONObject("data").getInt("LENGTH");
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return 0;
			}
		}
		else
			return 0;
	}

	public int playlistGetCurrent()
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"LCUR\"}")) != null)
		{
			try {
				return json.getJSONObject("data").getInt("POSITION");
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return 0;
			}
		}
		else
			return 0;
	}

	public JSONObject playlistGetInfo(int i)
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"LINF\", \"data\" : {\"POSITION\" : "+Integer.toString(i)+"}}")) != null)
		{
			try {
				return json.getJSONObject("data");
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		}
		else
			return null;
	}
	
	public boolean playlistAddTrack(String s)
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"LADD\", \"data\" : {\"FILE\" : \""+s+"\"}}")) != null)
		{
			try {
				return (json.getString("type").equals("response") && json.getString("command").equals("LADD"));
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	}

	public boolean playlistDelTrack(int i)
	{
		JSONObject json;
		if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"LDEL\", \"data\" : {\"POSITION\" : "+Integer.toString(i)+"}}")) != null)
		{
			try {
				return (json.getString("type").equals("response") && json.getString("command").equals("LADD"));
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
		else
			return false;
	}

	/*
	 * File browser
	 */
	
	public String browserGetCurrentPath()
	{
		return this.mCurrentPath;
	}
	
	public String browserGetCurrentPathAppendedWithSlash()
	{
		if(this.mCurrentPath.charAt(this.mCurrentPath.length()-1) != '/')
			return this.mCurrentPath+"/";
		else
			return this.mCurrentPath;
	}

	public JSONObject browserGetCurrentFilesAndFolders()
	{
		JSONObject json;
		try {
			if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"FLIS\", \"data\" : {\"PATH\" : \""+this.mCurrentPath+"\"}}")) != null && json.getString("type").equals("response") && json.getString("command").equals("FLIS"))
			{
				return json.getJSONObject("data");
			}
			else
				return null;
		} catch (JSONException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			return null;
		}
	}
	
	public boolean browserGoto(String dir)
	{
		// Always exists
		if(dir == "/")
		{
			this.mCurrentPath = dir;
			return true;
		}
		else
		{
			String path;
			// If relative: make absolute path
			if(dir.charAt(0) != '/')
				path = this.mCurrentPath+dir;
			else
				path = dir;

			if(path.charAt(path.length()-1) != '/')
				path += '/';
			
			// Check if path exists
			JSONObject json;
			try {
				if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"FCHE\", \"data\" : {\"PATH\" : \""+path+"\"}}")) != null && json.getString("type").equals("response") && json.getString("command").equals("FCHE"))
				{
					this.mCurrentPath = path;
					return json.getJSONObject("data").getBoolean("EXISTS");
				}
				else
					return false;
			} catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return false;
			}
		}
	}
	
	/**
	 * Get metadata about file
	 * @param absolute path to file
	 * @return JSONObject if the file was found, else null
	 */
	public JSONObject browserGetInfo(String file)
	{
		JSONObject json;
		try {
			if((json = makeRequest("{\"type\" : \"request\", \"command\" : \"FINF\", \"data\" : {\"FILE\" : \""+file+"\"}}")) != null && json.getString("type").equals("response") && json.getString("command").equals("FINF") && json.has("data"))
			{
				return json.getJSONObject("data");
			}
			else
				return null;
		}
		catch (JSONException e)
		{
			e.printStackTrace();
			return null;
		}
	}

	private JSONObject makeRequest(String s)
	{
		mOut.println(s);
		try {
			while(!mIn.ready()){} // Busy wait

			JSONObject json = new JSONObject(mIn.readLine());
			return json;
		}
		catch (Exception e)
		{
			e.printStackTrace();
			return null;
		}
	}
}

class NotPlayingException extends Exception {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4429780272588947873L;
}
